package luj.tool.jnproxyan.proxy.network.socksv2;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.socksx.SocksPortUnificationServerHandler;
import luj.cluster.api.actor.Tellable;
import luj.net.internal.server.bind.ServerBootMaker;
import luj.tool.jnproxyan.framework.lujnet.ConnState;
import luj.tool.jnproxyan.proxy.network.socksv2.connection.ConnStateAttr;
import luj.tool.jnproxyan.proxy.network.socksv2.server.ProxyServerState;
import luj.tool.jnproxyan.proxy.network.socksv2.server.handler.C2pLogHandler;
import luj.tool.jnproxyan.proxy.network.socksv2.server.handler.S5InitialReqHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicLong;

@Component
public class NettySocksStarter {

  public NettySocksStarter inject(String host, int port, Tellable socksRef) {
    var result = new NettySocksStarter();
    result._host = host;
    result._port = port;
    result._socksRef = socksRef;

    result._initReqHandler = _initReqHandler;
    return result;
  }

  /**
   * @see luj.tool.jnproxyan.proxy.network.socksv2.server.handler.S5CommandReqHandler#channelRead0
   */
  public void start() throws Exception {
    var loop = new NioEventLoopGroup();
    ServerBootstrap bootstrap = new ServerBootMaker(loop, loop).make();
    ProxyServerState serverState = makeServerState();

    bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
      @Override
      protected void initChannel(SocketChannel ch) {
        ConnState c2pConn = makeConnState(serverState);
        new ConnStateAttr(ch).init(c2pConn);

        ch.pipeline()
            .addLast(new C2pLogHandler())
            .addLast(new SocksPortUnificationServerHandler())
            .addLast(_initReqHandler.inject(_socksRef));
      }
    });

    bootstrap.bind(_host, _port).sync();
  }

  private ProxyServerState makeServerState() {
    var nextReqId = new AtomicLong(1);
    return new ProxyServerState(nextReqId);
  }

  private ConnState makeConnState(ProxyServerState server) {
    AtomicLong nextId = server.getNextReqId();
    Long reqId = nextId.getAndIncrement();

    return new ConnState(reqId, _socksRef, null);
  }

  String _host;
  int _port;

  Tellable _socksRef;

  @Autowired
  S5InitialReqHandler _initReqHandler;
}
